package rebalance;

import java.io.Serializable;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import remote.RemoteDistributedHashMap;
import remote.UnicastRemoteDistributedHashMap;

import log.Logger;
import messaging.InterNodeMessage;
import messaging.RebalanceUpdates;

import node.NodeManager;

public class Rebalance
{
  /**
   * Reference to node manager.
   */
  private NodeManager nodeman;
  /**
   * The actual node number.
   */
  private int actual_node_num;
  /**
   * Method key for re-balance updates for fault tolerance.
   */
  private int FAULT_TOLERANCE = 9;
  /**
   * constructor.
   */
  public Rebalance(NodeManager nm)
  {
    this.nodeman = nm;
    this.actual_node_num = nm.actual_node_number;
  }
  /**
   * Finds the list of nodes to simulate.
   * @return
   */
  private ArrayList<Integer> findNodesSimulate()
  {
    ArrayList<Integer> listToSend = new ArrayList<Integer>();
    int max_nodeNumber = Collections.max(nodeman.node_set) + 1;
    if (max_nodeNumber >= NodeManager.total_nodes_chord)
    {
      max_nodeNumber = 0;
    }
    listToSend.add(max_nodeNumber);
    int next = max_nodeNumber + 1;
    if (next >= NodeManager.total_nodes_chord)
    {
      next = 0;
    }
    //find all the nodes from this node to the next actual node.
    while (nodeman.rmi_NodeFind(next).endsWith("V"))
    {
      listToSend.add(next);
      next++;
      if (next >= NodeManager.total_nodes_chord)
      {
        next = 0;
      }
    }
    return listToSend;
  }
  /**
   * prints the contents of this hash map
   */
  public void printhashmap(Map<String, Serializable> map)
  {
    System.out.println(" printinghash map " + map.size());
    
    final Iterator iterator = map.keySet().iterator();  
    while (iterator.hasNext()) 
    {  
      final String key = iterator.next().toString();  
      final String value = (String) map.get(key); 
      System.out.println(key + " " + value);  
    }  
  }
  /**
   * 
   */
  public void rebalance()
  {
    Logger.out(" Inside the rebalance() method ");
    ArrayList<Integer> nodes_to_simulate = new ArrayList<Integer>();
    nodes_to_simulate = this.findNodesSimulate();
    Logger.out(this.nodeman.actual_node_number +  " The nodes to simulate are - ");
    Iterator itr = nodes_to_simulate.iterator();
    while (itr.hasNext())
    {
      Logger.out("Node - " + itr.next());
    }

    int sizestubs = nodeman.node_set.size();
    try
    {
      nodeman.registry.unbind(new Integer(nodes_to_simulate.get(0)).toString() + "A");
      for (int i = 0; i < nodes_to_simulate.size(); i++)
      { 
        int node_num = nodes_to_simulate.get(i);
        Logger.out(" the node being simulated " + node_num);
        nodeman.remoteArray[sizestubs] = new UnicastRemoteDistributedHashMap(node_num, 
                                                                             false, 
                                                                             nodeman.actual_node_number,
                                                                             nodeman);
        nodeman.node_stubs.put(node_num, nodeman.remoteArray[sizestubs]);
        nodeman.real_virtual_node_info.put(node_num, false);
        final RemoteDistributedHashMap stub = (RemoteDistributedHashMap) 
            UnicastRemoteObject.exportObject(nodeman.remoteArray[sizestubs], 0);
        nodeman.remoteArray[sizestubs].my_map.putAll(this.nodeman.duplicatestores.getNodeHashMap(node_num));
        //printhashmap(nodeman.remoteArray[sizestubs].my_map);
        Logger.out(node_num + " (virtual) is created and data added from the storage.");
        final String rmi_node_name = new Integer(node_num).toString() + "V";
        nodeman.registry.rebind(rmi_node_name, stub);
        Logger.out(rmi_node_name + " (virtual) is binded to the registry");
        nodeman.node_set.add(node_num);
        sizestubs++;
      }
    }
    catch (RemoteException e)
    {
      
    }
      //nodeman.registry.unbind(new Integer(nodes_to_simulate.get(0)).toString() + "A");
    catch (NotBoundException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
   
  }
  /**
   * This method unbinds the old nodes and re-binds them with the new node names.
   */
  public void registryupdates(int index)
  {
    try
    {
      for (int i = index; i < this.nodeman.node_set.size(); i++)
      {
        final String rmi_node_name = 
            new Integer(this.nodeman.node_set.get(i)).toString() + "V";
        final RemoteDistributedHashMap stub = (RemoteDistributedHashMap) 
            UnicastRemoteObject.exportObject(nodeman.remoteArray[i], 0);
        nodeman.registry.rebind(rmi_node_name, stub);
        Logger.out(rmi_node_name + " (virtual) is binded to the registry");
      }
    }
      catch (RemoteException e)
      {
        
      }
  }
  /**
   * Send a message to predecessor to update its successor by giving it the list of the new
   * successors and its data.
   */
  public void sendRebalanceUpdatesPredecessor()
  {
    //send all the new datas to the predecessor.
    int dest_node = this.nodeman.hashhelp.optimalNodeFind(this.actual_node_num, 
                                                          this.nodeman.predecessor_actual_node);
    final RebalanceUpdates reb_message = new RebalanceUpdates();
    reb_message.setpredsuccid(false);
    reb_message.setnewnodesets(this.nodeman.duplicatestores.successor_node_set);
    reb_message.setStorageData(this.nodeman.duplicatestores.storage);
    InterNodeMessage message = new InterNodeMessage(this.actual_node_num,
                                                    this.nodeman.predecessor_actual_node,
                                                    this.actual_node_num, dest_node,
                                                    this.FAULT_TOLERANCE);
    message.setRebUpdate(reb_message);
    final String dest_node_str = this.nodeman.rmi_NodeFind(dest_node);
    try
    {
      Logger.out(this.actual_node_num + " Sending rebalance updates to" +
          " predecessor node - " + this.nodeman.predecessor_actual_node +
          " for fault tolerance.");
      final RemoteDistributedHashMap to_send =
          (RemoteDistributedHashMap) NodeManager.registry.lookup(dest_node_str);
      to_send.send(message);
    }
    catch (final RemoteException e)
    {
      System.out.println("unable to send message in unicast sending ");
    }
    catch (NotBoundException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    catch (InterruptedException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

  }
  /**
   * This method finds the new successor for the actual node after re-balancing. 
   */
  public int findSuccessor()
  {
    int index = this.actual_node_num + 1;
    if (index == NodeManager.total_nodes_chord)
    {
      index = 0;
    }
    while (this.nodeman.rmi_NodeFind(index).endsWith("V"))
    {
      index++;
      if (index == NodeManager.total_nodes_chord)
      {
        index = 0;
      }
      continue;
    }
    return index;
  }
  /**
   * 
   */
  public int findnewSuccessor()
  {
    final int new_successor = this.nodeman.node_set.get(this.nodeman.node_set.size() - 1) + 1 % 
        this.nodeman.total_nodes_chord;
    return new_successor;
  }
  /**
   * Send message to successor about the update and get all the backup data from its new
   * successor.
   */
  public void sendRebalanceUpdatesSuccessor()
  {
    int new_successor = this.findSuccessor();
    //int new_successor = this.findnewSuccessor();
    int dest_node = this.nodeman.hashhelp.optimalNodeFind(this.actual_node_num, new_successor);
    final RebalanceUpdates reb_message = new RebalanceUpdates();
    reb_message.setpredsuccid(true);
    InterNodeMessage message = new InterNodeMessage(this.actual_node_num,
                                                    new_successor,
                                                    this.actual_node_num, dest_node,
                                                    this.FAULT_TOLERANCE);
    message.setRebUpdate(reb_message);
    final String dest_node_str = this.nodeman.rmi_NodeFind(dest_node);
    try
    {
      Logger.out(this.actual_node_num + " Sending rebalance updates to" +
          " successor node - " + new_successor + " for fault tolerance.");
      final RemoteDistributedHashMap to_send =
          (RemoteDistributedHashMap) NodeManager.registry.lookup(dest_node_str);
      to_send.send(message);
    }
    catch (final RemoteException e)
    {
      System.out.println("unable to send message in unicast sending ");
    }
    catch (NotBoundException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    catch (InterruptedException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

  }
}
